﻿using System;
using System.Collections.Generic;
using BrainTechLLC;
using System.Linq;
using System.IO;
using System.Text;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Media.Imaging;
using System.Collections.ObjectModel;
using System.Windows.Threading;
using System.Threading;

namespace WpfImageDuplicates
{
    public delegate void VoidDelegate();
    public delegate void OneParamDelegate(object o);

    public interface IWatcherNotifiable
    {
        void AddByFileName(string s);
        void RemoveByFileName(string s);
        void FileNameChanged(string oldFileName, string newFileName);
    }

    public class FileImageCollectionViewModel : ObservableCollection<FileImageViewModel>, IWatcherNotifiable
    {
        public event EventHandler ThumbnailsInvalidated;
        public Dispatcher _dispatcher;
        protected Thread _backgroundWorker;
        protected FileImageCollection _fileImageCollection = new FileImageCollection();
        protected DirectoriesToExamineViewModel _directoriesToExamine;
        protected bool _needRefresh;
        protected int _changeGeneration;
        protected object _lockObject = new object();
        protected List<DirectoryWatcher> _watchers;

        public bool NeedsRefresh { set { _needRefresh = value; } }

        public void AddByFileName(string s)
        {
            FileImage fileImage = FileImage.CreateNew(s);
            _fileImageCollection.Add(fileImage);
            FileImageViewModel vm = new FileImageViewModel(fileImage);
            Add(vm);
        }

        public void RemoveByFileName(string s)
        {
            var found = this.FirstOrDefault((vm) => vm.FileName.Equals(s, StringComparison.OrdinalIgnoreCase));

            if (found != null)
            {
                Remove(found);
            }

            var found2 = _fileImageCollection.FirstOrDefault((fi) => fi.FileName.Equals(s, StringComparison.OrdinalIgnoreCase));

            if (found2 != null)
            {
                _fileImageCollection.Remove(found2);
            }
        }

        public void FileNameChanged(string oldFileName, string newFileName)
        {
            // TODO
        }

        public FileImageCollectionViewModel(DirectoriesToExamineViewModel directoriesToExamine)
        {
            _directoriesToExamine = directoriesToExamine;
            _directoriesToExamine.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(_directoriesToExamine_CollectionChanged);
            ThreadStart ts = new ThreadStart(LazyLoad);
            _backgroundWorker = ts.CreateAndRunThread(true);
            _changeGeneration++;
            _needRefresh = true;
        }

        void _directoriesToExamine_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {         
            lock (_lockObject)
            {
                if (_watchers != null)
                {
                    foreach (var watch in _watchers)
                    {
                        watch.Detach(this);
                        watch.Dispose();
                    }
                }

                _changeGeneration++;
                _needRefresh = true;
            }
        }

        public void ClearAll()
        {
            ClearItems();
            _fileImageCollection.Clear();
        }

        void LazyLoad()
        {
            while (true)
            {
                while (!_needRefresh)
                {
                    Thread.Sleep(100);
                }

                _needRefresh = false;

                _dispatcher.BeginInvoke(new VoidDelegate(() => ClearAll()));

                while (_fileImageCollection.Count > 0)
                {
                    Thread.Sleep(10);
                }

                for (int n = 0; n < _directoriesToExamine.Count; n++)
                {
                    var path = _directoriesToExamine[n].Directory;
                    AddByPathNames(path);
                }
            }
        }

        public FileImageCollectionViewModel(DirectoriesToExamineViewModel directoriesToExamine, Dispatcher dispatcher)
            : this(directoriesToExamine)
        {
            _dispatcher = dispatcher;
        }

        public void AddByPath(string pathName)
        {
            var results = _fileImageCollection.AddByPath(pathName);
            AddWatcher(pathName);
            AddViewModelsFromFileImages(results, _changeGeneration);
        }

        public void AddWatchers(params string[] pathNames)
        {
            foreach (var pathName in pathNames)
            {
                AddWatcher(pathName);
            }
        }

        public void AddWatcher(string pathName)
        {
            if (_watchers == null)
            {
                Interlocked.CompareExchange<List<DirectoryWatcher>>(ref _watchers, new List<DirectoryWatcher>(), null);
            }

            lock (_lockObject)
            {
                var watch = new DirectoryWatcher(pathName, "*.jpg");
                _watchers.Add(watch);
                watch.Attach(this);
            }
        }

        public void AddByPathNames(params string[] pathNames)
        {
            var results = _fileImageCollection.AddByPathNames(pathNames);
            AddWatchers(pathNames);
            AddViewModelsFromFileImages(results, _changeGeneration);
        }

        public List<Pair<IFileImage, IFileImage>> FindDuplicates()
        {
            return _fileImageCollection.FindDuplicates();
        }

        public FileImageViewModel Find(string fileName)
        {
            for (int n = Count - 1; n >= 0; n--)
            {
                if (this[n].FileName == fileName)
                    return this[n];
            }

            return null;
        }

        protected int FindIndex(string fileName)
        {
            for (int n = Count - 1; n >= 0; n--)
            {
                if (this[n].FileName == fileName)
                    return n;
            }

            return -1;
        }

        public void InvalidateAllThumbnails()
        {            
            foreach (var item in this)
            {
                item.ClearThumbnail();
            }

            _directoriesToExamine.Refresh();

            if (ThumbnailsInvalidated != null)
                ThumbnailsInvalidated(this, EventArgs.Empty);
        }

        public FileImageViewModel GetNext(string fileName)
        {
            int idx = FindIndex(fileName);
            idx++;

            if (idx >= Count)
                return null;

            return this[idx];
        }

        public FileImageViewModel GetPrevious(string fileName)
        {
            int idx = FindIndex(fileName);
            idx--;

            if (idx < 0)
                return null;

            return this[idx];
        }

        public bool Delete(FileImageViewModel viewModel)
        {
            if (this.Remove(viewModel))
            {
                return _fileImageCollection.Delete(viewModel.FileImage);
            }

            return false;
        }

        public bool Delete(IEnumerable<FileImageViewModel> viewModels)
        {
            bool result = true;

            foreach (var viewModel in viewModels)
            {
                bool b = Delete(viewModel);
                result = result && b;
            }
            
            return result;
        }

        public void AddNew(IFileImage fileImage)
        {
            _fileImageCollection.Add(fileImage);
            this.Add(new FileImageViewModel(fileImage));
        }

        protected void AddViewModelsFromFileImages(List<IFileImage> collection, int currentGeneration)
        {
            if (_dispatcher != null)
            {
                for (int n = 0; n < collection.Count; n++)
                {
                    if (currentGeneration != _changeGeneration)
                        break;

                    _dispatcher.BeginInvoke(new OneParamDelegate((o) =>
                    {
                        if (currentGeneration != _changeGeneration)
                            return;

                        int i = (int)o;
                        var image = collection[i];
                        AddNew(image);
                    }), DispatcherPriority.ApplicationIdle, n);
                }
            }
            else
            {
                for (int n = 0; n < collection.Count; n++)
                {
                    var image = collection[n];
                    AddNew(image);
                }
            }
        }
    }
}
